package com.example.diskfilesystem.file;

import com.example.diskfilesystem.show.ShowAlert;
import config.Config;
import com.example.diskfilesystem.disk.Disk;
import com.example.diskfilesystem.disk.FATManager;
import com.example.diskfilesystem.directory.DirectoryManager;
import com.example.diskfilesystem.directory.DirectoryEntry;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FileManager {
    private final Disk disk;
    private final FATManager fat;
    private final DirectoryManager dirManager;
    private openfile openfile;//打开的文件表

    public FileManager(Disk disk, FATManager fat, DirectoryManager dirManager) {
        this.disk = disk;
        this.fat = fat;
        this.dirManager = dirManager;
        this.openfile = new openfile();
    }

    //创建新文件
    public boolean createFile(String name,String type,byte attribute,int parentBlock) throws IOException {

        //如果是只读文件不能建立
        if (attribute == DirectoryEntry.ATTR_sr || attribute == DirectoryEntry.ATTR_r) {
            //ShowAlert.showAlert("只读文件不能创建");
            System.out.println("只读文件不能创建");
            return false;
        }
        //读取父目录
        List<DirectoryEntry> entries = dirManager.readDirectoryEntries(parentBlock);
        //父目录不存在，建立失败 TODO

        //有重名文件创建失败（不过感觉可以设计自动+1副本)
        int idx = findEntry(entries, name, type);
        if (idx != -1) {
            System.out.println("文件已存在");
            return false;
        }


        //查找空闲目录项
        int freeSlot = -1;
        for (int i = 0; i < entries.size(); i++) {
            if (entries.get(i).isEmpty()) {
                freeSlot = i;
                break;
            }
        }
        if (freeSlot == -1) {
            System.out.println("目录项已满，创建失败");
            return false;
        }
        //建立文件目录
        //OFTLE newFile = new OFTLE(name, attribute, freeSlot, 1);
        //分配一个磁盘块
        //目录未满，则在磁盘空间中找寻空闲块
        int newBlock = fat.allocateBlock();
        if (newBlock == -1) {
            //ShowAlert.showAlert("磁盘空间已满，创建目录失败！");
            System.out.println("磁盘空间已满，创建目录失败！");
            return false;
        }
        //找到空闲块填写目录
        DirectoryEntry newFile = new DirectoryEntry(name, type, attribute, (byte) newBlock, (byte) 64);
        entries.set(freeSlot, newFile);
        dirManager.writeDirectoryEntries(parentBlock, entries);


        return true;
    }


    //打开文件
    public boolean openFile(String name,int flag,int parentBlock) throws IOException {
        //读取父目录
        List<DirectoryEntry> entries = dirManager.readDirectoryEntries(parentBlock);
        //文件不存在，打开失败
        int idx = findEntryWithoutType(entries, name);
        if (idx == -1) {
            System.out.println("文件不存在");
            return false;
        }
        //不能以写方式打开只读文件
        if (flag == OFTLE.WRITE) {
            if (entries.get(idx).getAttribute() == DirectoryEntry.ATTR_r || entries.get(idx).getAttribute() == DirectoryEntry.ATTR_sr) {
                System.out.println("只读文件不能以写方式打开");
                return false;
            }
        }
        //填写已打开文件表，如果文件已打开不用填写,如果更改打开方式，修改打开方式
        if (openfile.findOpenFile(name) != -1) {
            if (flag != openfile.getFiles().get(openfile.findOpenFile(name)).flag) {
                openfile.getFiles().get(openfile.findOpenFile(name)).setFlag(flag);
                System.out.println("文件打开方式已更改");
                return true;
            }
            System.out.println("文件已打开");
            return true;
        }
        openfile.addOpenFile(new OFTLE(name, entries.get(idx).getAttribute(), entries.get(idx).getStartBlock(), flag));
        System.out.println("文件打开成功");

        return true;
    }

    //读取文件内容（'#' 截止，否则读到 length × BLOCK_SIZE）
    public String readFile(String name,int length, int parentBlock) throws IOException {
        //是否在打开文件表中
        int idx = openfile.findOpenFile(name);
        //不在打开文件表中，尝试打开
        if (idx == -1) {
            openFile(name,OFTLE.READ,parentBlock);
        }
        //找到该文件打开方式，写方式不允许读
        if(openfile.getFiles().get(openfile.findOpenFile(name)).getFlag() != OFTLE.READ){
            System.out.println("文件未以读方式打开");
            return null;
        }
        //读出读指针，从这个位置上读出所需要长度，若所需长度没有读完已经遇到文件结束符，就终止操作
        //找到文件起始盘块号
        int fileBlock = openfile.getFiles().get(openfile.findOpenFile(name)).getNumber();
        byte[] buffer = new byte[length];
        //读取第一个块
        int bytesRead = 0;
        int currentBlock = fileBlock;
        //一块一块读，直到读满或者遇到文件结束符
        while (bytesRead < buffer.length && currentBlock != Config.END) {
            //读取当前块数据
            byte[] blockData = disk.readBlock(currentBlock);
            //在当前块（64字节）中逐字节读取
            for (byte b : blockData) {
                if (b == '#') { // 文件结束符
                    String content = new String(buffer, 0, bytesRead);
                    //System.out.printf("Read %d bytes from file '%s':%n%s%n", bytesRead, name, content);
                    return content;
                }
                if (bytesRead < buffer.length) {
                    buffer[bytesRead++] = b;
                } else {
                    break;
                }
            }
            //移动到下一个块
            currentBlock = fat.getNextBlock(currentBlock);
        }
        return new String(buffer, 0, bytesRead);
    }

    //写文件
    public boolean writeFile(String name,byte[] content,int length,int parentBlock) throws IOException {
        //是否在打开文件表中
        int idx = openfile.findOpenFile(name);
        //不在打开文件表中，尝试打开
        if (idx == -1) {
            openFile(name,OFTLE.WRITE,parentBlock);
        }
        //找到该文件打开方式，写方式不允许读
        if(openfile.getFiles().get(openfile.findOpenFile(name)).getFlag() != OFTLE.WRITE){
            System.out.println("文件未以写方式打开");
            return false;
        }
        //读出写指针
        Pointer writePointer = openfile.getFiles().get(openfile.findOpenFile(name)).getWrite();
        //找到文件起始盘块号
        int fileBlock = openfile.getFiles().get(openfile.findOpenFile(name)).getNumber();
        //读取第一个块
        //读取当前块数据
        byte[] blockData = disk.readBlock(fileBlock);
        //记录旧文件长度
        int oldLength = openfile.getFiles().get(openfile.findOpenFile(name)).getLength();
        //如果文件没写任何内容,直接写
        if(writePointer.getBnum() == 0) {
            //写入内容
            int bytesWritten = writePointer.getBnum();
            int currentBlock = fileBlock;
            while (bytesWritten < length) {
                //写入当前块数据
                for (int i = writePointer.getBnum(); i < Config.BLOCK_SIZE; i++) {
                    if (bytesWritten < length) {
                        blockData[i] = content[bytesWritten++];
                        writePointer.setBnum(bytesWritten%Config.BLOCK_SIZE);
                    } else {
                        //结尾添加文件结束符
                        if (i < Config.BLOCK_SIZE) {
                            blockData[i] = '#';
                        }
                        break;
                    }
                }
                //写回当前块
                disk.writeBlock(currentBlock, blockData);
                //如果内容没写完，分配新块
                if (bytesWritten < length) {
                    int newBlock = fat.allocateBlock();
                    if (newBlock == -1) {
                        System.out.println("磁盘空间已满，写入失败");
                        return false;
                    }
                    //更新 FAT 表
                    fat.setNextBlock(currentBlock, newBlock);
                    currentBlock = newBlock;
                    //准备写入新块
                    blockData = new byte[Config.BLOCK_SIZE];
                    writePointer.setDnum(newBlock);
                    writePointer.setBnum(0);
                } else {
                    //写完了，更新 FAT 表末尾
                    fat.setNextBlock(currentBlock, Config.END);
                    //更新文件长度
                    openfile.getFiles().get(openfile.findOpenFile(name)).setLength(length);
                }
            }

        } else {
            //文件写了一部分内容,从写指针位置开始写入内容
            int bytesWritten = writePointer.getBnum();
            int currentBlock = writePointer.getDnum();
            int newlength = length + bytesWritten;
            blockData = disk.readBlock(currentBlock);
            int written = 0,i;
            //尝试填满当前块
            while (bytesWritten < newlength && bytesWritten < Config.BLOCK_SIZE) {
                //写入当前块数据
                for (i = writePointer.getBnum(); i < Config.BLOCK_SIZE; i++) {
                    //int contentIndex = ;
                    if (i < length) {
                        blockData[i] = content[i - bytesWritten];
                        writePointer.setBnum(bytesWritten%Config.BLOCK_SIZE);
                    } else {
                        //结尾添加文件结束符
                        if (i - bytesWritten < Config.BLOCK_SIZE) {
                            blockData[i] = '#';
                            bytesWritten = i;
                            writePointer.setBnum(writePointer.getBnum()+newlength);
                            break;
                        }
                    }
                }
                written += i;
                //写回当前块
                disk.writeBlock(currentBlock, blockData);
                //如果内容没写完，分配新块
                if (written < newlength) {


                    int newBlock = fat.allocateBlock();
                    if (newBlock == -1) {
                        System.out.println("磁盘空间已满，写入失败");
                        return false;
                    }
                    //更新 FAT 表
                    fat.setNextBlock(currentBlock, newBlock);
                    currentBlock = newBlock;
                    //准备写入新块
                    blockData = new byte[Config.BLOCK_SIZE];
                    writePointer.setDnum(newBlock);
                    writePointer.setBnum(0);

                    //content-64个字节
                    byte[] newContent = Arrays.copyOfRange(content, written-bytesWritten, length);
                    written += writeNewFile(name, newContent, newlength - written);
                    bytesWritten = written;
                    //写完了，更新 FAT 表末尾
                    //fat.setNextBlock(currentBlock, Config.END);
                    //更新文件长度

                    openfile.getFiles().get(openfile.findOpenFile(name)).setLength(oldLength+length);
                }
            }
        }
        return true;
    }
    //分配新块写入内容
    public int writeNewFile(String name,byte[] content,int length) throws IOException {
        //读出写指针
        Pointer writePointer = openfile.getFiles().get(openfile.findOpenFile(name)).getWrite();
        //找到当前盘块号
        int currentBlock = writePointer.getDnum();
        //读取第一个块
        //读取当前块数据
        byte[] blockData = disk.readBlock(currentBlock);
        //如果文件没写任何内容,直接写
        if(writePointer.getBnum() == 0) {
            //写入内容
            int bytesWritten = writePointer.getBnum();
            while (bytesWritten < length) {
                //写入当前块数据
                for (int i = writePointer.getBnum(); i < Config.BLOCK_SIZE; i++) {
                    if (bytesWritten < length) {
                        blockData[i] = content[bytesWritten++];
                        writePointer.setBnum(bytesWritten%Config.BLOCK_SIZE);
                    } else {
                        //结尾添加文件结束符
                        if (i < Config.BLOCK_SIZE) {
                            blockData[i] = '#';
                        }
                        break;
                    }
                }
                //写回当前块
                disk.writeBlock(currentBlock, blockData);
                //如果内容没写完，分配新块
                if (bytesWritten < length) {
                    int newBlock = fat.allocateBlock();
                    if (newBlock == -1) {
                        System.out.println("磁盘空间已满，写入失败");
                        return bytesWritten;
                    }
                    //更新 FAT 表
                    fat.setNextBlock(currentBlock, newBlock);
                    currentBlock = newBlock;
                    //准备写入新块
                    blockData = new byte[Config.BLOCK_SIZE];
                    writePointer.setDnum(newBlock);
                    writePointer.setBnum(0);
                } else {
                    //写完了，更新 FAT 表末尾
                    fat.setNextBlock(currentBlock, Config.END);
                    //更新文件长度
                    openfile.getFiles().get(openfile.findOpenFile(name)).setLength(length);
                }
            }

        }
        return length;
    }
    //删除文件
    public boolean deleteFile(String name, String type,int parentBlock) throws IOException {
        //查找文件是否存在
        List<DirectoryEntry> entries = dirManager.readDirectoryEntries(parentBlock);
        int idx = findEntry(entries, name, type);
        if (idx == -1) {
            System.out.println("文件不存在");
            return false;
        }
        //检查是否打开，打开不能删除
        if (openfile.findOpenFile(name) != -1) {
            System.out.println("文件已打开，不能删除");
            return false;
        }
        //找到目录项
        DirectoryEntry entry = entries.get(idx);
        // 释放 FAT 链
        fat.freeChain(entry.getStartBlock());
        // 清空目录项
        entries.set(idx, DirectoryEntry.emptyEntry());
        dirManager.writeDirectoryEntries(parentBlock, entries);

        return true;
    }

    //显示文件内容
    public boolean typeFile(int parentBlock, String name, String type){
        //找到目录登记项，不存在，显示失败 TODO

        //是否打开文件，打开不显示 TODO

        //未打开，则从目录取出文件起始盘块号，一块一块显示文件内容 TODO
        return true;
    }

    //改变文件属性
    public boolean changeAttribute(String name,String type,int newAttribute){
        //找到目录登记项，不存在，显示失败 TODO

        //检查是否打开，打开不能更改属性 TODO

        //没打开根据要求更改属性 TODO

        return true;
    }

    //是否合法文件名
    private boolean isValidName(String name) {
        return name != null  && !name.contains("$") && !name.contains(".") && !name.contains("/");
    }
    //是否合法文件类型
    private boolean isValidType(String type) {
        return type != null;
    }

    //寻找空闲目录项
    private int findFreeSlot(List<DirectoryEntry> entries) {
        for (int i = 0; i < entries.size(); i++) {
            if (entries.get(i).isEmpty()) return i;
        }
        return -1;
    }

    //根据文件名和类型寻找目录项
    private int findEntry(List<DirectoryEntry> entries, String name, String type) {
        for (int i = 0; i < entries.size(); i++) {
            DirectoryEntry e = entries.get(i);
            if (!e.isEmpty() &&
                    e.getFileName().equalsIgnoreCase(name) &&
                    e.getFileType().equalsIgnoreCase(type)) {
                return i;
            }
        }
        return -1;
    }

    //根据文件名寻找目录项(不区分类型)
    private int findEntryWithoutType(List<DirectoryEntry> entries, String name) {
        for (int i = 0; i < entries.size(); i++) {
            DirectoryEntry e = entries.get(i);
            if (!e.isEmpty() &&
                    e.getFileName().equalsIgnoreCase(name)) {
                return i;
            }
        }
        return -1;
    }

    public Disk getDisk() {
        return disk;
    }

    public FATManager getFat() {
        return fat;
    }

    public DirectoryManager getDirManager() {
        return dirManager;
    }

    public openfile getOpenfile() {
        return openfile;
    }

    public void setOpenfile(openfile openfile) {
        this.openfile = openfile;
    }
}
